Micron Document
NexusPi Git Node

Node / mirrors / RTNode-HeltecV4 / files / BoundaryConfig.h

Displaying Raw • Download

BoundaryConfig.h copilot/add-rtnode-configuration-options (9529fa8a) Text, 32.35 KB

T8b949e// Copyright (C) 2026, Boundary Mode Extension
T8b949e// Based on microReticulum_Firmware by Mark Qvist
T8b949e//
T8b949e// BoundaryConfig.h — Captive-portal web configuration for the legacy
T8b949e// "Boundary Mode" path. This should be renamed to "Transport Mode"
T8b949e// together with the rest of the boundary-mode terminology. In this fork,
T8b949e// transport/boundary mode is the only intended mode of operation.
T8b949e// When triggered (first boot with no config, or button hold >5s),
T8b949e// the device starts a WiFi AP with a web form for all settings:
T8b949e// WiFi STA credentials, TCP backbone params, LoRa radio params,
T8b949e// and optional AP-mode TCP server.
T8b949e//
T8b949e// This program is free software: you can redistribute it and/or modify
T8b949e// it under the terms of the GNU General Public License as published by
T8b949e// the Free Software Foundation, either version 3 of the License, or
T8b949e// (at your option) any later version.

Tff7b72#Tff7b72ifndef BOUNDARY_CONFIG_H
Tff7b72#Tff7b72define BOUNDARY_CONFIG_H

Tff7b72#Tff7b72ifdef BOUNDARY_MODE

Tff7b72#Tff7b72include T8b949e<WiFi.h>
Tff7b72#Tff7b72include T8b949e<WebServer.h>
Tff7b72#Tff7b72include T8b949e<DNSServer.h>

T8b949e// ─── Node hash (cached in RTC by normal boot, read here without starting RNS) ─
Tff7b72#Tff7b72define NODE_HASH_RTC_MAGIC 0x504B4841UL
Tff7b72extern Tffa657uint32_t Te6edf3rtc_node_hash_magicTb4b4b4;
Tff7b72extern Tffa657char Te6edf3rtc_node_hash_hexTb4b4b4[T79c0ff33Tb4b4b4]Tb4b4b4;

T8b949e// ─── Config Portal State ─────────────────────────────────────────────────────
Tff7b72static Tffa657bool Te6edf3config_portal_active Tff7b72= Tffa657falseTb4b4b4;
Tff7b72static Te6edf3WebServerTff7b72* Te6edf3config_server Tff7b72= Tffa657nullptrTb4b4b4;
Tff7b72static Te6edf3DNSServerTff7b72* Te6edf3config_dns Tff7b72= Tffa657nullptrTb4b4b4;

Tff7b72static Tff7b72const Tffa657char Te6edf3CONFIG_AP_SSIDTb4b4b4[Tb4b4b4] Tff7b72= Ta5d6ff"Ta5d6ffRTNode-SetupTa5d6ff"Tb4b4b4;
Tff7b72static Tff7b72const Tffa657uint16_t Te6edf3DNS_PORT Tff7b72= T79c0ff53Tb4b4b4;
Tff7b72static Tff7b72const Tffa657uint16_t Te6edf3HTTP_PORT Tff7b72= T79c0ff80Tb4b4b4;

T8b949e// Forward declarations
Tffa657void Td2a8ffconfig_portal_startTb4b4b4(Tb4b4b4)Tb4b4b4;
Tffa657void Td2a8ffconfig_portal_stopTb4b4b4(Tb4b4b4)Tb4b4b4;
Tffa657void Td2a8ffconfig_portal_loopTb4b4b4(Tb4b4b4)Tb4b4b4;
Tffa657bool Td2a8ffconfig_portal_is_activeTb4b4b4(Tb4b4b4)Tb4b4b4;
Tffa657bool Td2a8ffboundary_needs_configTb4b4b4(Tb4b4b4)Tb4b4b4;

T8b949e// ─── Common bandwidth values (Hz) ───────────────────────────────────────────
T8b949e// These match Reticulum standard channel plans
T8b949e// Stored in flash (PROGMEM) to save ~200 bytes of RAM
Tff7b72static Tff7b72const Tffa657uint32_t Te6edf3BW_OPTIONS_HZTb4b4b4[Tb4b4b4] Te6edf3PROGMEM Tff7b72= Tb4b4b4{
T79c0ff7800Tb4b4b4, T79c0ff10400Tb4b4b4, T79c0ff15600Tb4b4b4, T79c0ff20800Tb4b4b4, T79c0ff31250Tb4b4b4, T79c0ff41700Tb4b4b4, T79c0ff62500Tb4b4b4, T79c0ff125000Tb4b4b4, T79c0ff250000Tb4b4b4, T79c0ff500000Tb4b4b4,
Tb4b4b4}Tb4b4b4;
Tff7b72static Tff7b72const Tffa657char Te6edf3BW_LABEL_0Tb4b4b4[Tb4b4b4] Te6edf3PROGMEM Tff7b72= Ta5d6ff"Ta5d6ff7.8 kHzTa5d6ff"Tb4b4b4;
Tff7b72static Tff7b72const Tffa657char Te6edf3BW_LABEL_1Tb4b4b4[Tb4b4b4] Te6edf3PROGMEM Tff7b72= Ta5d6ff"Ta5d6ff10.4 kHzTa5d6ff"Tb4b4b4;
Tff7b72static Tff7b72const Tffa657char Te6edf3BW_LABEL_2Tb4b4b4[Tb4b4b4] Te6edf3PROGMEM Tff7b72= Ta5d6ff"Ta5d6ff15.6 kHzTa5d6ff"Tb4b4b4;
Tff7b72static Tff7b72const Tffa657char Te6edf3BW_LABEL_3Tb4b4b4[Tb4b4b4] Te6edf3PROGMEM Tff7b72= Ta5d6ff"Ta5d6ff20.8 kHzTa5d6ff"Tb4b4b4;
Tff7b72static Tff7b72const Tffa657char Te6edf3BW_LABEL_4Tb4b4b4[Tb4b4b4] Te6edf3PROGMEM Tff7b72= Ta5d6ff"Ta5d6ff31.25 kHzTa5d6ff"Tb4b4b4;
Tff7b72static Tff7b72const Tffa657char Te6edf3BW_LABEL_5Tb4b4b4[Tb4b4b4] Te6edf3PROGMEM Tff7b72= Ta5d6ff"Ta5d6ff41.7 kHzTa5d6ff"Tb4b4b4;
Tff7b72static Tff7b72const Tffa657char Te6edf3BW_LABEL_6Tb4b4b4[Tb4b4b4] Te6edf3PROGMEM Tff7b72= Ta5d6ff"Ta5d6ff62.5 kHzTa5d6ff"Tb4b4b4;
Tff7b72static Tff7b72const Tffa657char Te6edf3BW_LABEL_7Tb4b4b4[Tb4b4b4] Te6edf3PROGMEM Tff7b72= Ta5d6ff"Ta5d6ff125 kHzTa5d6ff"Tb4b4b4;
Tff7b72static Tff7b72const Tffa657char Te6edf3BW_LABEL_8Tb4b4b4[Tb4b4b4] Te6edf3PROGMEM Tff7b72= Ta5d6ff"Ta5d6ff250 kHzTa5d6ff"Tb4b4b4;
Tff7b72static Tff7b72const Tffa657char Te6edf3BW_LABEL_9Tb4b4b4[Tb4b4b4] Te6edf3PROGMEM Tff7b72= Ta5d6ff"Ta5d6ff500 kHzTa5d6ff"Tb4b4b4;
Tff7b72static Tff7b72const Tffa657charTff7b72* Tff7b72const Te6edf3BW_OPTIONS_LABELSTb4b4b4[Tb4b4b4] Te6edf3PROGMEM Tff7b72= Tb4b4b4{
Te6edf3BW_LABEL_0Tb4b4b4, Te6edf3BW_LABEL_1Tb4b4b4, Te6edf3BW_LABEL_2Tb4b4b4, Te6edf3BW_LABEL_3Tb4b4b4, Te6edf3BW_LABEL_4Tb4b4b4,
Te6edf3BW_LABEL_5Tb4b4b4, Te6edf3BW_LABEL_6Tb4b4b4, Te6edf3BW_LABEL_7Tb4b4b4, Te6edf3BW_LABEL_8Tb4b4b4, Te6edf3BW_LABEL_9Tb4b4b4,
Tb4b4b4}Tb4b4b4;
Tff7b72static Tff7b72const Tffa657int Te6edf3BW_OPTIONS_COUNT Tff7b72= Tff7b72sizeofTb4b4b4(Te6edf3BW_OPTIONS_HZTb4b4b4) Tff7b72/ Tff7b72sizeofTb4b4b4(Te6edf3BW_OPTIONS_HZTb4b4b4[T79c0ff0Tb4b4b4]Tb4b4b4)Tb4b4b4;

Tff7b72static Tffa657uint8_t Td2a8ffconfig_default_display_rotationTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72#Tff7b72if BOARD_MODEL == BOARD_LORA32_V2_1 || BOARD_MODEL == BOARD_TBEAM || BOARD_MODEL == BOARD_RAK4631
Tff7b72return T79c0ff0Tb4b4b4;
Tff7b72#Tff7b72elif BOARD_MODEL == BOARD_HELTEC32_V2 || BOARD_MODEL == BOARD_HELTEC32_V3 || BOARD_MODEL == BOARD_HELTEC32_V4 || BOARD_MODEL == BOARD_HELTEC_T114 || BOARD_MODEL == BOARD_TBEAM_S_V1
Tff7b72return T79c0ff1Tb4b4b4;
Tff7b72#Tff7b72else
Tff7b72return T79c0ff3Tb4b4b4;
Tff7b72#Tff7b72endif
Tb4b4b4}

T8b949e// ─── HTML Page Generation ────────────────────────────────────────────────────

Tff7b72static Tffa657void Td2a8ffconfig_send_htmlTb4b4b4(Tb4b4b4) Tb4b4b4{
T8b949e// Read current values from EEPROM/globals for pre-population
Tffa657char Te6edf3cur_ssidTb4b4b4[T79c0ff33Tb4b4b4] Tff7b72= Ta5d6ff"Ta5d6ff"Tb4b4b4;
Tffa657char Te6edf3cur_pskTb4b4b4[T79c0ff33Tb4b4b4] Tff7b72= Ta5d6ff"Ta5d6ff"Tb4b4b4;

Tff7b72for Tb4b4b4(Tffa657int Te6edf3i Tff7b72= T79c0ff0Tb4b4b4; Te6edf3i Tff7b72< T79c0ff32Tb4b4b4; Te6edf3iTff7b72+Tff7b72+Tb4b4b4) Tb4b4b4{
Te6edf3cur_ssidTb4b4b4[Te6edf3iTb4b4b4] Tff7b72= Te6edf3EEPROMTb4b4b4.Te6edf3readTb4b4b4(Te6edf3config_addrTb4b4b4(Te6edf3ADDR_CONF_SSID Tff7b72+ Te6edf3iTb4b4b4)Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3cur_ssidTb4b4b4[Te6edf3iTb4b4b4] Tff7b72=Tff7b72= Tb4b4b4(Tffa657charTb4b4b4)T79c0ff0xFFTb4b4b4) Te6edf3cur_ssidTb4b4b4[Te6edf3iTb4b4b4] Tff7b72= Ta5d6ff'Ta5d6ff\0Ta5d6ff'Tb4b4b4;
Tb4b4b4}
Te6edf3cur_ssidTb4b4b4[T79c0ff32Tb4b4b4] Tff7b72= Ta5d6ff'Ta5d6ff\0Ta5d6ff'Tb4b4b4;

Tff7b72for Tb4b4b4(Tffa657int Te6edf3i Tff7b72= T79c0ff0Tb4b4b4; Te6edf3i Tff7b72< T79c0ff32Tb4b4b4; Te6edf3iTff7b72+Tff7b72+Tb4b4b4) Tb4b4b4{
Te6edf3cur_pskTb4b4b4[Te6edf3iTb4b4b4] Tff7b72= Te6edf3EEPROMTb4b4b4.Te6edf3readTb4b4b4(Te6edf3config_addrTb4b4b4(Te6edf3ADDR_CONF_PSK Tff7b72+ Te6edf3iTb4b4b4)Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3cur_pskTb4b4b4[Te6edf3iTb4b4b4] Tff7b72=Tff7b72= Tb4b4b4(Tffa657charTb4b4b4)T79c0ff0xFFTb4b4b4) Te6edf3cur_pskTb4b4b4[Te6edf3iTb4b4b4] Tff7b72= Ta5d6ff'Ta5d6ff\0Ta5d6ff'Tb4b4b4;
Tb4b4b4}
Te6edf3cur_pskTb4b4b4[T79c0ff32Tb4b4b4] Tff7b72= Ta5d6ff'Ta5d6ff\0Ta5d6ff'Tb4b4b4;

T8b949e// Current LoRa values (from globals, which were loaded from EEPROM)
Tffa657uint32_t Te6edf3cur_freq Tff7b72= Te6edf3lora_freqTb4b4b4;
Tffa657uint32_t Te6edf3cur_bw Tff7b72= Te6edf3lora_bwTb4b4b4;
Tffa657int Te6edf3cur_sf Tff7b72= Te6edf3lora_sfTb4b4b4;
Tffa657int Te6edf3cur_cr Tff7b72= Te6edf3lora_crTb4b4b4;
Tffa657int Te6edf3cur_txp Tff7b72= Te6edf3lora_txpTb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3cur_txp Tff7b72=Tff7b72= T79c0ff0xFFTb4b4b4) Te6edf3cur_txp Tff7b72= Te6edf3PA_MAX_OUTPUTTb4b4b4; T8b949e// Default to board max

T8b949e// Default frequency if not set
Tff7b72if Tb4b4b4(Te6edf3cur_freq Tff7b72=Tff7b72= T79c0ff0Tb4b4b4) Te6edf3cur_freq Tff7b72= T79c0ff914875000Tb4b4b4; T8b949e// 914.875 MHz default
Tff7b72if Tb4b4b4(Te6edf3cur_bw Tff7b72=Tff7b72= T79c0ff0Tb4b4b4) Te6edf3cur_bw Tff7b72= T79c0ff125000Tb4b4b4; T8b949e// 125 kHz default
Tff7b72if Tb4b4b4(Te6edf3cur_sf Tff7b72=Tff7b72= T79c0ff0Tb4b4b4) Te6edf3cur_sf Tff7b72= T79c0ff10Tb4b4b4; T8b949e// SF10 default
Tff7b72if Tb4b4b4(Te6edf3cur_cr Tff7b72< T79c0ff5 Tff7b72|Tff7b72| Te6edf3cur_cr Tff7b72> T79c0ff8Tb4b4b4) Te6edf3cur_cr Tff7b72= T79c0ff5Tb4b4b4; T8b949e// CR 4/5 default

T8b949e// Build the HTML page
Te6edf3String Te6edf3html Tff7b72= Te6edf3FTb4b4b4(
Ta5d6ff"Ta5d6ff<!DOCTYPE html><html><head>Ta5d6ff"
Ta5d6ff"Ta5d6ff<meta name='viewport' content='width=device-width,initial-scale=1'>Ta5d6ff"
Ta5d6ff"Ta5d6ff<title>RTNode Setup</title>Ta5d6ff"
Ta5d6ff"Ta5d6ff<style>Ta5d6ff"
Ta5d6ff"Ta5d6ffbody{font-family:sans-serif;background:#1a1a2e;color:#e0e0e0;margin:0;padding:16px;}Ta5d6ff"
Ta5d6ff"Ta5d6ffh1{color:#e94560;font-size:1.4em;margin:0 0 8px;}Ta5d6ff"
Ta5d6ff"Ta5d6ffh2{color:#0f3460;background:#e0e0e0;padding:6px 10px;margin:18px -10px 10px;font-size:1em;border-radius:4px;}Ta5d6ff"
Ta5d6ff"Ta5d6ffform{max-width:480px;margin:0 auto;}Ta5d6ff"
Ta5d6ff"Ta5d6fflabel{display:block;margin:8px 0 2px;font-size:0.9em;color:#aaa;}Ta5d6ff"
Ta5d6ff"Ta5d6ffinput,select{width:100%;padding:8px;margin:2px 0 6px;box-sizing:border-box;Ta5d6ff"
Ta5d6ff"Ta5d6ffbackground:#16213e;border:1px solid #0f3460;color:#e0e0e0;border-radius:4px;font-size:0.95em;}Ta5d6ff"
Ta5d6ff"Ta5d6ffinput:focus,select:focus{border-color:#e94560;outline:none;}Ta5d6ff"
Ta5d6ff"Ta5d6ff.row{display:flex;gap:10px;}.row>div{flex:1;}Ta5d6ff"
Ta5d6ff"Ta5d6ff.note{font-size:0.8em;color:#666;margin:2px 0 8px;}Ta5d6ff"
Ta5d6ff"Ta5d6ffbutton{width:100%;padding:12px;margin:20px 0;background:#e94560;color:#fff;Ta5d6ff"
Ta5d6ff"Ta5d6ffborder:none;border-radius:4px;font-size:1.1em;cursor:pointer;}Ta5d6ff"
Ta5d6ff"Ta5d6ffbutton:hover{background:#c73e54;}Ta5d6ff"
Ta5d6ff"Ta5d6ff.ok{background:#16213e;padding:20px;border-radius:8px;text-align:center;}Ta5d6ff"
Ta5d6ff"Ta5d6ff.ok h1{color:#0f0;}Ta5d6ff"
Ta5d6ff"Ta5d6ff.node-hash{background:#0f1a30;border:1px solid #0f3460;border-radius:6px;Ta5d6ff"
Ta5d6ff"Ta5d6ffpadding:10px 14px;margin:0 0 16px;}Ta5d6ff"
Ta5d6ff"Ta5d6ff.node-hash .nh-label{display:block;font-size:0.75em;color:#888;margin-bottom:4px;}Ta5d6ff"
Ta5d6ff"Ta5d6ff.node-hash code{font-family:monospace;font-size:0.95em;color:#7ecfff;Ta5d6ff"
Ta5d6ff"Ta5d6ffword-break:break-all;letter-spacing:0.05em;}Ta5d6ff"
Ta5d6ff"Ta5d6ff</style></head><body>Ta5d6ff"
Ta5d6ff"Ta5d6ff<h1>&#x1f4e1; RTNode</h1>Ta5d6ff"
Tb4b4b4)Tb4b4b4;

T8b949e// ── Node public hash ──
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<div class='node-hash'><span class='nh-label'>&#x1f511; Node Hash (Reticulum destination)</span><code>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3rtc_node_hash_magic Tff7b72=Tff7b72= Te6edf3NODE_HASH_RTC_MAGIC Tff7b72&Tff7b72& Te6edf3rtc_node_hash_hexTb4b4b4[T79c0ff0Tb4b4b4] Tff7b72!Tff7b72= Ta5d6ff'Ta5d6ff\0Ta5d6ff'Tb4b4b4) Tb4b4b4{
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3rtc_node_hash_hexTb4b4b4)Tb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<span style='color:#888;font-style:italic;'>Not yet assigned &mdash; will be set on first normal boot</span>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</code></div>Ta5d6ff"Tb4b4b4)Tb4b4b4;

Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<form method='POST' action='/save'>Ta5d6ff"Tb4b4b4)Tb4b4b4;

T8b949e// ── Node Name Section ──
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(
Ta5d6ff"Ta5d6ff<h2>&#x1f3f7; Node Name</h2>Ta5d6ff"
Ta5d6ff"Ta5d6ff<p class='note'>A human-readable name for this node, shown in advertisements and on maps Ta5d6ff"
Ta5d6ff"Ta5d6ffsuch as <a href='https://rmap.world' target='_blank' style='color:#7ecfff'>rmap.world</a>. Ta5d6ff"
Ta5d6ff"Ta5d6ffLeave blank to auto-generate a name from the node hash.</p>Ta5d6ff"
Ta5d6ff"Ta5d6ff<label>Name</label>Ta5d6ff"
Ta5d6ff"Ta5d6ff<input name='node_name' maxlength='32' placeholder='e.g. My RNode' value='Ta5d6ff"
Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3boundary_stateTb4b4b4.Te6edf3node_nameTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff'>Ta5d6ff"Tb4b4b4)Tb4b4b4;

Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(
Ta5d6ff"Ta5d6ff<h2>&#x1f4f6; WiFi Network</h2>Ta5d6ff"
Ta5d6ff"Ta5d6ff<label>WiFi</label>Ta5d6ff"
Ta5d6ff"Ta5d6ff<select name='wifi_en'>Ta5d6ff"
Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<option value='1'Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3boundary_stateTb4b4b4.Te6edf3wifi_enabledTb4b4b4) Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff selectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff>Enabled</option>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<option value='0'Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3boundary_stateTb4b4b4.Te6edf3wifi_enabledTb4b4b4) Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff selectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff>Disabled (LoRa-only repeater)</option>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</select>Ta5d6ff"Tb4b4b4)Tb4b4b4;

Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(
Ta5d6ff"Ta5d6ff<label>SSID</label>Ta5d6ff"
Ta5d6ff"Ta5d6ff<input name='ssid' maxlength='32' placeholder='Your WiFi network' value='Ta5d6ff"
Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3cur_ssidTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(
Ta5d6ff"Ta5d6ff'>Ta5d6ff"
Ta5d6ff"Ta5d6ff<label>Password</label>Ta5d6ff"
Ta5d6ff"Ta5d6ff<input name='psk' type='password' maxlength='32' placeholder='WiFi password' value='Ta5d6ff"
Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3cur_pskTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff'>Ta5d6ff"Tb4b4b4)Tb4b4b4;

T8b949e// ── TCP Backbone Section ──
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(
Ta5d6ff"Ta5d6ff<h2>&#x1f310; TCP Backbone</h2>Ta5d6ff"
Ta5d6ff"Ta5d6ff<label>Mode</label>Ta5d6ff"
Ta5d6ff"Ta5d6ff<select name='tcp_mode'>Ta5d6ff"
Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<option value='0'Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3boundary_stateTb4b4b4.Te6edf3tcp_mode Tff7b72=Tff7b72= T79c0ff0Tb4b4b4) Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff selectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff>Disabled</option>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<option value='1'Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3boundary_stateTb4b4b4.Te6edf3tcp_mode Tff7b72=Tff7b72= T79c0ff1Tb4b4b4) Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff selectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff>Client (connect to backbone)</option>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</select>Ta5d6ff"Tb4b4b4)Tb4b4b4;

Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<label>Backbone Host</label>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<input name='bb_host' maxlength='63' placeholder='e.g. 192.168.1.100' value='Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3boundary_stateTb4b4b4.Te6edf3backbone_hostTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff'>Ta5d6ff"Tb4b4b4)Tb4b4b4;

Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<label>Backbone Port</label>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<input name='bb_port' type='number' min='1' max='65535' value='Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3boundary_stateTb4b4b4.Te6edf3backbone_portTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff'>Ta5d6ff"Tb4b4b4)Tb4b4b4;

T8b949e// ── Local TCP Server Section ──
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(
Ta5d6ff"Ta5d6ff<h2>&#x1f4e1; Local TCP Server (optional)</h2>Ta5d6ff"
Ta5d6ff"Ta5d6ff<p class='note'>Run a TCP server on the same WiFi network so local devices can connect. Ta5d6ff"
Ta5d6ff"Ta5d6ffUses Access Point mode (does not forward announces).</p>Ta5d6ff"
Ta5d6ff"Ta5d6ff<label>Local TCP Server</label>Ta5d6ff"
Ta5d6ff"Ta5d6ff<select name='ap_tcp_en'>Ta5d6ff"
Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<option value='0'Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3boundary_stateTb4b4b4.Te6edf3ap_tcp_enabledTb4b4b4) Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff selectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff>Disabled</option>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<option value='1'Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3boundary_stateTb4b4b4.Te6edf3ap_tcp_enabledTb4b4b4) Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff selectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff>Enabled</option>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</select>Ta5d6ff"Tb4b4b4)Tb4b4b4;

Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<label>TCP Port</label>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<input name='ap_tcp_port' type='number' min='1' max='65535' value='Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3boundary_stateTb4b4b4.Te6edf3ap_tcp_portTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff'>Ta5d6ff"Tb4b4b4)Tb4b4b4;

T8b949e// ── LoRa Radio Section ──
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(
Ta5d6ff"Ta5d6ff<h2>&#x1f4fb; LoRa Radio</h2>Ta5d6ff"
Tb4b4b4)Tb4b4b4;

T8b949e// Frequency — show in MHz for human-friendliness
Tffa657char Te6edf3freq_strTb4b4b4[T79c0ff16Tb4b4b4]Tb4b4b4;
Te6edf3dtostrfTb4b4b4(Tb4b4b4(Tffa657doubleTb4b4b4)Te6edf3cur_freq Tff7b72/ T79c0ff1000000.0Tb4b4b4, T79c0ff1Tb4b4b4, T79c0ff3Tb4b4b4, Te6edf3freq_strTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<label>Frequency (MHz)</label>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<input name='freq' type='text' placeholder='914.875' value='Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3freq_strTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff'>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<p class='note'>e.g. 914.875, 868.000, 433.000</p>Ta5d6ff"Tb4b4b4)Tb4b4b4;

T8b949e// Bandwidth — dropdown
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<label>Bandwidth</label><select name='bw'>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72for Tb4b4b4(Tffa657int Te6edf3i Tff7b72= T79c0ff0Tb4b4b4; Te6edf3i Tff7b72< Te6edf3BW_OPTIONS_COUNTTb4b4b4; Te6edf3iTff7b72+Tff7b72+Tb4b4b4) Tb4b4b4{
Tffa657uint32_t Te6edf3bw_hz Tff7b72= Te6edf3pgm_read_dwordTb4b4b4(Tff7b72&Te6edf3BW_OPTIONS_HZTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4)Tb4b4b4;
Tffa657char Te6edf3label_bufTb4b4b4[T79c0ff16Tb4b4b4]Tb4b4b4;
Te6edf3strncpy_PTb4b4b4(Te6edf3label_bufTb4b4b4, Tb4b4b4(Tff7b72const Tffa657charTff7b72*Tb4b4b4)Te6edf3pgm_read_ptrTb4b4b4(Tff7b72&Te6edf3BW_OPTIONS_LABELSTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4)Tb4b4b4, Tff7b72sizeofTb4b4b4(Te6edf3label_bufTb4b4b4)T79c0ff-1Tb4b4b4)Tb4b4b4;
Te6edf3label_bufTb4b4b4[Tff7b72sizeofTb4b4b4(Te6edf3label_bufTb4b4b4)T79c0ff-1Tb4b4b4] Tff7b72= Ta5d6ff'Ta5d6ff\0Ta5d6ff'Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<option value='Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3bw_hzTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Ta5d6ff"Ta5d6ff'Ta5d6ff"Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3bw_hz Tff7b72=Tff7b72= Te6edf3cur_bwTb4b4b4) Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff selectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Ta5d6ff"Ta5d6ff>Ta5d6ff"Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3label_bufTb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</option>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</select>Ta5d6ff"Tb4b4b4)Tb4b4b4;

T8b949e// Spreading Factor — dropdown 5-12
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<label>Spreading Factor</label><select name='sf'>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72for Tb4b4b4(Tffa657int Te6edf3sf Tff7b72= T79c0ff5Tb4b4b4; Te6edf3sf Tff7b72<Tff7b72= T79c0ff12Tb4b4b4; Te6edf3sfTff7b72+Tff7b72+Tb4b4b4) Tb4b4b4{
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<option value='Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3sfTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Ta5d6ff"Ta5d6ff'Ta5d6ff"Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3sf Tff7b72=Tff7b72= Te6edf3cur_sfTb4b4b4) Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff selectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Ta5d6ff"Ta5d6ff>SFTa5d6ff"Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3sfTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</option>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</select>Ta5d6ff"Tb4b4b4)Tb4b4b4;

T8b949e// Coding Rate — dropdown 5-8 (maps to 4/5 through 4/8)
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<label>Coding Rate</label><select name='cr'>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72for Tb4b4b4(Tffa657int Te6edf3cr Tff7b72= T79c0ff5Tb4b4b4; Te6edf3cr Tff7b72<Tff7b72= T79c0ff8Tb4b4b4; Te6edf3crTff7b72+Tff7b72+Tb4b4b4) Tb4b4b4{
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<option value='Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3crTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Ta5d6ff"Ta5d6ff'Ta5d6ff"Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3cr Tff7b72=Tff7b72= Te6edf3cur_crTb4b4b4) Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff selectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Ta5d6ff"Ta5d6ff>4/Ta5d6ff"Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3crTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</option>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</select>Ta5d6ff"Tb4b4b4)Tb4b4b4;

T8b949e// TX Power
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<label>TX Power (dBm)</label>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<input name='txp' type='number' min='2' max='Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72#Tff7b72ifdef PA_MAX_OUTPUT
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3PA_MAX_OUTPUTTb4b4b4)Tb4b4b4;
Tff7b72#Tff7b72else
Te6edf3html Tff7b72+Tff7b72= Ta5d6ff"Ta5d6ff22Ta5d6ff"Tb4b4b4;
Tff7b72#Tff7b72endif
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff' value='Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3cur_txpTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff'>Ta5d6ff"Tb4b4b4)Tb4b4b4;

Tff7b72#Tff7b72ifdef PA_MAX_OUTPUT
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<p class='note'>Max output for this board: Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3PA_MAX_OUTPUTTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff dBm (with PA)</p>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72#Tff7b72endif

T8b949e// ── IFAC (Interface Access Code) Section ──
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(
Ta5d6ff"Ta5d6ff<h2>&#x1f512; Network Access (IFAC)</h2>Ta5d6ff"
Ta5d6ff"Ta5d6ff<p class='note'>Set a network name and/or passphrase to restrict LoRa interface access. Ta5d6ff"
Ta5d6ff"Ta5d6ffOnly nodes with matching settings can communicate. Both fields are optional.</p>Ta5d6ff"
Ta5d6ff"Ta5d6ff<label>IFAC</label>Ta5d6ff"
Ta5d6ff"Ta5d6ff<select name='ifac_en'>Ta5d6ff"
Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<option value='0'Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3boundary_stateTb4b4b4.Te6edf3ifac_enabledTb4b4b4) Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff selectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff>Disabled</option>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<option value='1'Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3boundary_stateTb4b4b4.Te6edf3ifac_enabledTb4b4b4) Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff selectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff>Enabled</option>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</select>Ta5d6ff"Tb4b4b4)Tb4b4b4;

Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<label>Network Name</label>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<input name='ifac_name' maxlength='32' placeholder='e.g. MyNetwork' value='Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3boundary_stateTb4b4b4.Te6edf3ifac_netnameTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff'>Ta5d6ff"Tb4b4b4)Tb4b4b4;

Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<label>Passphrase</label>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<input name='ifac_pass' type='password' maxlength='32' placeholder='Shared secret' value='Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3boundary_stateTb4b4b4.Te6edf3ifac_passphraseTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff'>Ta5d6ff"Tb4b4b4)Tb4b4b4;

T8b949e// ── Device Advertisement Section ──
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(
Ta5d6ff"Ta5d6ff<h2>&#x1f4cd; Device Advertisement</h2>Ta5d6ff"
Ta5d6ff"Ta5d6ff<p class='note'>Advertise this node and its parameters on the Reticulum network. Ta5d6ff"
Ta5d6ff"Ta5d6ffMaps such as <a href='https://rmap.world' target='_blank' style='color:#7ecfff'>rmap.world</a> Ta5d6ff"
Ta5d6ff"Ta5d6ffuse these announcements to automatically place a pin for the node. Ta5d6ff"
Ta5d6ff"Ta5d6ffDisabled by default; enable only if you want this node to be publicly listed.</p>Ta5d6ff"
Ta5d6ff"Ta5d6ff<label>Advertise Device</label>Ta5d6ff"
Ta5d6ff"Ta5d6ff<select name='advert_en'>Ta5d6ff"
Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<option value='0'Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3boundary_stateTb4b4b4.Te6edf3advert_enabledTb4b4b4) Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff selectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff>Disabled</option>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<option value='1'Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3boundary_stateTb4b4b4.Te6edf3advert_enabledTb4b4b4) Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff selectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff>Enabled</option>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</select>Ta5d6ff"Tb4b4b4)Tb4b4b4;

T8b949e// Latitude / Longitude — pre-populate with current values, but keep
T8b949e// the inputs blank when the user has not yet set coordinates so the
T8b949e// browser placeholder hint is visible.
Tffa657char Te6edf3lat_strTb4b4b4[T79c0ff32Tb4b4b4]Tb4b4b4;
Tffa657char Te6edf3lon_strTb4b4b4[T79c0ff32Tb4b4b4]Tb4b4b4;
Te6edf3lat_strTb4b4b4[T79c0ff0Tb4b4b4] Tff7b72= Ta5d6ff'Ta5d6ff\0Ta5d6ff'Tb4b4b4;
Te6edf3lon_strTb4b4b4[T79c0ff0Tb4b4b4] Tff7b72= Ta5d6ff'Ta5d6ff\0Ta5d6ff'Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3boundary_stateTb4b4b4.Te6edf3advert_enabled Tff7b72|Tff7b72|
Te6edf3boundary_stateTb4b4b4.Te6edf3advert_lat Tff7b72!Tff7b72= T79c0ff0.0 Tff7b72|Tff7b72|
Te6edf3boundary_stateTb4b4b4.Te6edf3advert_lon Tff7b72!Tff7b72= T79c0ff0.0Tb4b4b4) Tb4b4b4{
Te6edf3dtostrfTb4b4b4(Te6edf3boundary_stateTb4b4b4.Te6edf3advert_latTb4b4b4, T79c0ff1Tb4b4b4, T79c0ff6Tb4b4b4, Te6edf3lat_strTb4b4b4)Tb4b4b4;
Te6edf3dtostrfTb4b4b4(Te6edf3boundary_stateTb4b4b4.Te6edf3advert_lonTb4b4b4, T79c0ff1Tb4b4b4, T79c0ff6Tb4b4b4, Te6edf3lon_strTb4b4b4)Tb4b4b4;
Tb4b4b4}

Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<div class='row'>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<div><label>Latitude (&deg;)</label>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<input id='advert_lat' name='advert_lat' type='text' inputmode='decimal' Ta5d6ff"
Ta5d6ff"Ta5d6ffplaceholder='e.g. 37.774929' value='Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3lat_strTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff'></div>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<div><label>Longitude (&deg;)</label>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<input id='advert_lon' name='advert_lon' type='text' inputmode='decimal' Ta5d6ff"
Ta5d6ff"Ta5d6ffplaceholder='e.g. -122.419416' value='Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3lon_strTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff'></div>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</div>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<p class='note'>Decimal degrees, signed. North/East positive, South/West negative. Ta5d6ff"
Ta5d6ff"Ta5d6ffLeave both blank to omit coordinates.</p>Ta5d6ff"Tb4b4b4)Tb4b4b4;

Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<label>Randomize Offset</label>Ta5d6ff"
Ta5d6ff"Ta5d6ff<select name='advert_jitter'>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<option value='0'Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3boundary_stateTb4b4b4.Te6edf3advert_jitterTb4b4b4) Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff selectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff>Disabled</option>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<option value='1'Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3boundary_stateTb4b4b4.Te6edf3advert_jitterTb4b4b4) Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff selectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff>Enabled (~0.5 km / 0.5 mi)</option>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</select>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<p class='note'>When enabled, the advertised coordinates are shifted by a Ta5d6ff"
Ta5d6ff"Ta5d6ffrandom offset of approximately half a kilometre (about half a mile) for Ta5d6ff"
Ta5d6ff"Ta5d6ffprivacy. The exact stored coordinates are not changed.</p>Ta5d6ff"Tb4b4b4)Tb4b4b4;

T8b949e// ── Options Section ──
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(
Ta5d6ff"Ta5d6ff<h2>&#x2699; Options</h2>Ta5d6ff"
Ta5d6ff"Ta5d6ff<label>Display Blanking</label>Ta5d6ff"
Ta5d6ff"Ta5d6ff<select name='disp_blank'>Ta5d6ff"
Tb4b4b4)Tb4b4b4;

T8b949e// Read current blanking timeout from EEPROM (stored as minutes, 0 = never)
Tffa657uint8_t Te6edf3cur_blank Tff7b72= T79c0ff5Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3EEPROMTb4b4b4.Te6edf3readTb4b4b4(Te6edf3eeprom_addrTb4b4b4(Te6edf3ADDR_CONF_BSETTb4b4b4)Tb4b4b4) Tff7b72=Tff7b72= Te6edf3CONF_OK_BYTETb4b4b4) Tb4b4b4{
Te6edf3cur_blank Tff7b72= Te6edf3EEPROMTb4b4b4.Te6edf3readTb4b4b4(Te6edf3eeprom_addrTb4b4b4(Te6edf3ADDR_CONF_DBLKTb4b4b4)Tb4b4b4)Tb4b4b4;
Tb4b4b4}

Tffa657uint8_t Te6edf3cur_rotation Tff7b72= Te6edf3EEPROMTb4b4b4.Te6edf3readTb4b4b4(Te6edf3eeprom_addrTb4b4b4(Te6edf3ADDR_CONF_DROTTb4b4b4)Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3cur_rotation Tff7b72> T79c0ff3Tb4b4b4) Tb4b4b4{
Te6edf3cur_rotation Tff7b72= Te6edf3config_default_display_rotationTb4b4b4(Tb4b4b4)Tb4b4b4;
Tb4b4b4}

Tff7b72static Tff7b72const Tffa657uint8_t Te6edf3blank_valsTb4b4b4[Tb4b4b4] Tff7b72= Tb4b4b4{ T79c0ff0Tb4b4b4, T79c0ff1Tb4b4b4, T79c0ff5Tb4b4b4, T79c0ff10Tb4b4b4, T79c0ff30Tb4b4b4, T79c0ff60 Tb4b4b4}Tb4b4b4;
Tff7b72static Tff7b72const Tffa657charTff7b72* Te6edf3blank_labelsTb4b4b4[Tb4b4b4] Tff7b72= Tb4b4b4{ Ta5d6ff"Ta5d6ffNeverTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff1 minuteTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff5 minutesTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff10 minutesTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff30 minutesTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff60 minutesTa5d6ff" Tb4b4b4}Tb4b4b4;
Tff7b72static Tff7b72const Tffa657int Te6edf3blank_count Tff7b72= T79c0ff6Tb4b4b4;

Tff7b72for Tb4b4b4(Tffa657int Te6edf3i Tff7b72= T79c0ff0Tb4b4b4; Te6edf3i Tff7b72< Te6edf3blank_countTb4b4b4; Te6edf3iTff7b72+Tff7b72+Tb4b4b4) Tb4b4b4{
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<option value='Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3blank_valsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Ta5d6ff"Ta5d6ff'Ta5d6ff"Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3blank_valsTb4b4b4[Te6edf3iTb4b4b4] Tff7b72=Tff7b72= Te6edf3cur_blankTb4b4b4) Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff selectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Ta5d6ff"Ta5d6ff>Ta5d6ff"Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3blank_labelsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</option>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</select>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<p class='note'>Turn off display after inactivity to save power</p>Ta5d6ff"Tb4b4b4)Tb4b4b4;

Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<label>Display Orientation</label><select name='disp_rot'>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<option value='0'Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3cur_rotation Tff7b72=Tff7b72= T79c0ff0Tb4b4b4) Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff selectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff>Landscape</option>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<option value='1'Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3cur_rotation Tff7b72=Tff7b72= T79c0ff1Tb4b4b4) Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff selectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff>Portrait</option>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<option value='2'Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3cur_rotation Tff7b72=Tff7b72= T79c0ff2Tb4b4b4) Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff selectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff>Landscape Flipped</option>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<option value='3'Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3cur_rotation Tff7b72=Tff7b72= T79c0ff3Tb4b4b4) Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff selectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff>Portrait Flipped</option>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</select>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<p class='note'>Choose the orientation that matches your OLED mounting. Ta5d6ff"
Ta5d6ff"Ta5d6ffLandscape modes place the two status panes side by side; portrait modes stack them.</p>Ta5d6ff"Tb4b4b4)Tb4b4b4;

T8b949e// ── Submit ──
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(
Ta5d6ff"Ta5d6ff<button type='submit'>Save &amp; Reboot</button>Ta5d6ff"
Ta5d6ff"Ta5d6ff</form></body></html>Ta5d6ff"
Tb4b4b4)Tb4b4b4;

Te6edf3config_serverTff7b72-Tff7b72>Te6edf3sendTb4b4b4(T79c0ff200Tb4b4b4, Ta5d6ff"Ta5d6fftext/htmlTa5d6ff"Tb4b4b4, Te6edf3htmlTb4b4b4)Tb4b4b4;
Tb4b4b4}

T8b949e// ─── Handle POST /save ──────────────────────────────────────────────────────

Tff7b72static Tffa657void Td2a8ffconfig_handle_saveTb4b4b4(Tb4b4b4) Tb4b4b4{
T8b949e// ── WiFi STA credentials ──
Te6edf3String Te6edf3ssid Tff7b72= Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6ffssidTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3String Te6edf3psk Tff7b72= Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6ffpskTa5d6ff"Tb4b4b4)Tb4b4b4;

T8b949e// Write SSID to config EEPROM area
Tff7b72for Tb4b4b4(Tffa657int Te6edf3i Tff7b72= T79c0ff0Tb4b4b4; Te6edf3i Tff7b72< T79c0ff32Tb4b4b4; Te6edf3iTff7b72+Tff7b72+Tb4b4b4) Tb4b4b4{
Tffa657uint8_t Te6edf3c Tff7b72= Tb4b4b4(Te6edf3i Tff7b72< Tb4b4b4(Tffa657intTb4b4b4)Te6edf3ssidTb4b4b4.Te6edf3lengthTb4b4b4(Tb4b4b4)Tb4b4b4) Tff7b72? Te6edf3ssidTb4b4b4[Te6edf3iTb4b4b4] Tff7b72: T79c0ff0x00Tb4b4b4;
Te6edf3EEPROMTb4b4b4.Te6edf3writeTb4b4b4(Te6edf3config_addrTb4b4b4(Te6edf3ADDR_CONF_SSID Tff7b72+ Te6edf3iTb4b4b4)Tb4b4b4, Te6edf3cTb4b4b4)Tb4b4b4;
Tb4b4b4}
Te6edf3EEPROMTb4b4b4.Te6edf3writeTb4b4b4(Te6edf3config_addrTb4b4b4(Te6edf3ADDR_CONF_SSID Tff7b72+ T79c0ff32Tb4b4b4)Tb4b4b4, T79c0ff0x00Tb4b4b4)Tb4b4b4;

T8b949e// Write PSK
Tff7b72for Tb4b4b4(Tffa657int Te6edf3i Tff7b72= T79c0ff0Tb4b4b4; Te6edf3i Tff7b72< T79c0ff32Tb4b4b4; Te6edf3iTff7b72+Tff7b72+Tb4b4b4) Tb4b4b4{
Tffa657uint8_t Te6edf3c Tff7b72= Tb4b4b4(Te6edf3i Tff7b72< Tb4b4b4(Tffa657intTb4b4b4)Te6edf3pskTb4b4b4.Te6edf3lengthTb4b4b4(Tb4b4b4)Tb4b4b4) Tff7b72? Te6edf3pskTb4b4b4[Te6edf3iTb4b4b4] Tff7b72: T79c0ff0x00Tb4b4b4;
Te6edf3EEPROMTb4b4b4.Te6edf3writeTb4b4b4(Te6edf3config_addrTb4b4b4(Te6edf3ADDR_CONF_PSK Tff7b72+ Te6edf3iTb4b4b4)Tb4b4b4, Te6edf3cTb4b4b4)Tb4b4b4;
Tb4b4b4}
Te6edf3EEPROMTb4b4b4.Te6edf3writeTb4b4b4(Te6edf3config_addrTb4b4b4(Te6edf3ADDR_CONF_PSK Tff7b72+ T79c0ff32Tb4b4b4)Tb4b4b4, T79c0ff0x00Tb4b4b4)Tb4b4b4;

T8b949e// Set WiFi mode to STA
Te6edf3EEPROMTb4b4b4.Te6edf3writeTb4b4b4(Te6edf3eeprom_addrTb4b4b4(Te6edf3ADDR_CONF_WIFITb4b4b4)Tb4b4b4, Te6edf3WR_WIFI_STATb4b4b4)Tb4b4b4;

T8b949e// Boundary mode always uses DHCP on the STA interface. Clear the legacy
T8b949e// static IP and netmask slots so stale values from older firmware or tools
T8b949e// cannot force a persistent static address.
Tff7b72for Tb4b4b4(Tffa657int Te6edf3i Tff7b72= T79c0ff0Tb4b4b4; Te6edf3i Tff7b72< T79c0ff4Tb4b4b4; Te6edf3iTff7b72+Tff7b72+Tb4b4b4) Tb4b4b4{
Te6edf3EEPROMTb4b4b4.Te6edf3writeTb4b4b4(Te6edf3config_addrTb4b4b4(Te6edf3ADDR_CONF_IP Tff7b72+ Te6edf3iTb4b4b4)Tb4b4b4, T79c0ff0x00Tb4b4b4)Tb4b4b4;
Te6edf3EEPROMTb4b4b4.Te6edf3writeTb4b4b4(Te6edf3config_addrTb4b4b4(Te6edf3ADDR_CONF_NM Tff7b72+ Te6edf3iTb4b4b4)Tb4b4b4, T79c0ff0x00Tb4b4b4)Tb4b4b4;
Tb4b4b4}

T8b949e// ── WiFi enable setting ──
Te6edf3boundary_stateTb4b4b4.Te6edf3wifi_enabled Tff7b72= Tb4b4b4(Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6ffwifi_enTa5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4) Tff7b72=Tff7b72= T79c0ff1Tb4b4b4)Tb4b4b4;

T8b949e// ── Display blanking (EEPROM stores minutes, 0 = disabled) ──
Tffa657int Te6edf3blank_minutes Tff7b72= Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6ffdisp_blankTa5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3blank_minutes Tff7b72<Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{
Te6edf3display_blanking_enabled Tff7b72= Tffa657falseTb4b4b4;
Te6edf3eeprom_updateTb4b4b4(Te6edf3eeprom_addrTb4b4b4(Te6edf3ADDR_CONF_BSETTb4b4b4)Tb4b4b4, Te6edf3CONF_OK_BYTETb4b4b4)Tb4b4b4;
Te6edf3eeprom_updateTb4b4b4(Te6edf3eeprom_addrTb4b4b4(Te6edf3ADDR_CONF_DBLKTb4b4b4)Tb4b4b4, T79c0ff0Tb4b4b4)Tb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
Tffa657uint8_t Te6edf3blank_val Tff7b72= Tb4b4b4(Tffa657uint8_tTb4b4b4)Tb4b4b4(Te6edf3blank_minutes Tff7b72> T79c0ff255 Tff7b72? T79c0ff255 Tff7b72: Te6edf3blank_minutesTb4b4b4)Tb4b4b4;
Te6edf3display_blanking_enabled Tff7b72= Tffa657trueTb4b4b4;
Te6edf3display_blanking_timeout Tff7b72= Tb4b4b4(Tffa657uint32_tTb4b4b4)Te6edf3blank_val Tff7b72* T79c0ff60UL Tff7b72* T79c0ff1000ULTb4b4b4;
Te6edf3eeprom_updateTb4b4b4(Te6edf3eeprom_addrTb4b4b4(Te6edf3ADDR_CONF_BSETTb4b4b4)Tb4b4b4, Te6edf3CONF_OK_BYTETb4b4b4)Tb4b4b4;
Te6edf3eeprom_updateTb4b4b4(Te6edf3eeprom_addrTb4b4b4(Te6edf3ADDR_CONF_DBLKTb4b4b4)Tb4b4b4, Te6edf3blank_valTb4b4b4)Tb4b4b4;
Tb4b4b4}

Tffa657int Te6edf3display_rotation Tff7b72= Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6ffdisp_rotTa5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3display_rotation Tff7b72< T79c0ff0 Tff7b72|Tff7b72| Te6edf3display_rotation Tff7b72> T79c0ff3Tb4b4b4) Tb4b4b4{
Te6edf3display_rotation Tff7b72= Te6edf3config_default_display_rotationTb4b4b4(Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Te6edf3eeprom_updateTb4b4b4(Te6edf3eeprom_addrTb4b4b4(Te6edf3ADDR_CONF_DROTTb4b4b4)Tb4b4b4, Tb4b4b4(Tffa657uint8_tTb4b4b4)Te6edf3display_rotationTb4b4b4)Tb4b4b4;

T8b949e// ── TCP backbone settings ──
Te6edf3boundary_stateTb4b4b4.Te6edf3tcp_mode Tff7b72= Tb4b4b4(Tffa657uint8_tTb4b4b4)Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6fftcp_modeTa5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4)Tb4b4b4; T8b949e// 0=disabled, 1=client
Tff7b72if Tb4b4b4(Te6edf3boundary_stateTb4b4b4.Te6edf3tcp_mode Tff7b72> T79c0ff1Tb4b4b4) Te6edf3boundary_stateTb4b4b4.Te6edf3tcp_mode Tff7b72= T79c0ff0Tb4b4b4;
Te6edf3boundary_stateTb4b4b4.Te6edf3tcp_port Tff7b72= Tb4b4b4(Tffa657uint16_tTb4b4b4)Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6fftcp_portTa5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3boundary_stateTb4b4b4.Te6edf3tcp_port Tff7b72=Tff7b72= T79c0ff0Tb4b4b4) Te6edf3boundary_stateTb4b4b4.Te6edf3tcp_port Tff7b72= T79c0ff4242Tb4b4b4;

Te6edf3String Te6edf3bb_host Tff7b72= Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6ffbb_hostTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3memsetTb4b4b4(Te6edf3boundary_stateTb4b4b4.Te6edf3backbone_hostTb4b4b4, T79c0ff0Tb4b4b4, Tff7b72sizeofTb4b4b4(Te6edf3boundary_stateTb4b4b4.Te6edf3backbone_hostTb4b4b4)Tb4b4b4)Tb4b4b4;
Te6edf3strncpyTb4b4b4(Te6edf3boundary_stateTb4b4b4.Te6edf3backbone_hostTb4b4b4, Te6edf3bb_hostTb4b4b4.Te6edf3c_strTb4b4b4(Tb4b4b4)Tb4b4b4, Tff7b72sizeofTb4b4b4(Te6edf3boundary_stateTb4b4b4.Te6edf3backbone_hostTb4b4b4) Tff7b72- T79c0ff1Tb4b4b4)Tb4b4b4;

Te6edf3boundary_stateTb4b4b4.Te6edf3backbone_port Tff7b72= Tb4b4b4(Tffa657uint16_tTb4b4b4)Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6ffbb_portTa5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3boundary_stateTb4b4b4.Te6edf3backbone_port Tff7b72=Tff7b72= T79c0ff0Tb4b4b4) Te6edf3boundary_stateTb4b4b4.Te6edf3backbone_port Tff7b72= T79c0ff4242Tb4b4b4;

T8b949e// ── Local TCP server settings ──
Te6edf3boundary_stateTb4b4b4.Te6edf3ap_tcp_enabled Tff7b72= Tb4b4b4(Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6ffap_tcp_enTa5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4) Tff7b72=Tff7b72= T79c0ff1Tb4b4b4)Tb4b4b4;
Te6edf3boundary_stateTb4b4b4.Te6edf3ap_tcp_port Tff7b72= Tb4b4b4(Tffa657uint16_tTb4b4b4)Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6ffap_tcp_portTa5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3boundary_stateTb4b4b4.Te6edf3ap_tcp_port Tff7b72=Tff7b72= T79c0ff0Tb4b4b4) Te6edf3boundary_stateTb4b4b4.Te6edf3ap_tcp_port Tff7b72= T79c0ff4242Tb4b4b4;

T8b949e// ── IFAC settings ──
Te6edf3boundary_stateTb4b4b4.Te6edf3ifac_enabled Tff7b72= Tb4b4b4(Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6ffifac_enTa5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4) Tff7b72=Tff7b72= T79c0ff1Tb4b4b4)Tb4b4b4;

Te6edf3String Te6edf3ifac_name Tff7b72= Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6ffifac_nameTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3memsetTb4b4b4(Te6edf3boundary_stateTb4b4b4.Te6edf3ifac_netnameTb4b4b4, T79c0ff0Tb4b4b4, Tff7b72sizeofTb4b4b4(Te6edf3boundary_stateTb4b4b4.Te6edf3ifac_netnameTb4b4b4)Tb4b4b4)Tb4b4b4;
Te6edf3strncpyTb4b4b4(Te6edf3boundary_stateTb4b4b4.Te6edf3ifac_netnameTb4b4b4, Te6edf3ifac_nameTb4b4b4.Te6edf3c_strTb4b4b4(Tb4b4b4)Tb4b4b4, Tff7b72sizeofTb4b4b4(Te6edf3boundary_stateTb4b4b4.Te6edf3ifac_netnameTb4b4b4) Tff7b72- T79c0ff1Tb4b4b4)Tb4b4b4;

Te6edf3String Te6edf3ifac_pass Tff7b72= Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6ffifac_passTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3memsetTb4b4b4(Te6edf3boundary_stateTb4b4b4.Te6edf3ifac_passphraseTb4b4b4, T79c0ff0Tb4b4b4, Tff7b72sizeofTb4b4b4(Te6edf3boundary_stateTb4b4b4.Te6edf3ifac_passphraseTb4b4b4)Tb4b4b4)Tb4b4b4;
Te6edf3strncpyTb4b4b4(Te6edf3boundary_stateTb4b4b4.Te6edf3ifac_passphraseTb4b4b4, Te6edf3ifac_passTb4b4b4.Te6edf3c_strTb4b4b4(Tb4b4b4)Tb4b4b4, Tff7b72sizeofTb4b4b4(Te6edf3boundary_stateTb4b4b4.Te6edf3ifac_passphraseTb4b4b4) Tff7b72- T79c0ff1Tb4b4b4)Tb4b4b4;

T8b949e// If IFAC is enabled but both fields are empty, disable it
Tff7b72if Tb4b4b4(Te6edf3boundary_stateTb4b4b4.Te6edf3ifac_enabled Tff7b72&Tff7b72&
Te6edf3boundary_stateTb4b4b4.Te6edf3ifac_netnameTb4b4b4[T79c0ff0Tb4b4b4] Tff7b72=Tff7b72= Ta5d6ff'Ta5d6ff\0Ta5d6ff' Tff7b72&Tff7b72&
Te6edf3boundary_stateTb4b4b4.Te6edf3ifac_passphraseTb4b4b4[T79c0ff0Tb4b4b4] Tff7b72=Tff7b72= Ta5d6ff'Ta5d6ff\0Ta5d6ff'Tb4b4b4) Tb4b4b4{
Te6edf3boundary_stateTb4b4b4.Te6edf3ifac_enabled Tff7b72= Tffa657falseTb4b4b4;
Tb4b4b4}

T8b949e// ── Device advertisement settings ──
Te6edf3boundary_stateTb4b4b4.Te6edf3advert_enabled Tff7b72= Tb4b4b4(Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6ffadvert_enTa5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4) Tff7b72=Tff7b72= T79c0ff1Tb4b4b4)Tb4b4b4;

T8b949e// Empty lat/lon strings are treated as "not set" → 0.0. Otherwise parse
T8b949e// and clamp to valid ranges; out-of-range values are silently coerced
T8b949e// to 0.0 rather than rejecting the whole save.
Te6edf3String Te6edf3lat_arg Tff7b72= Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6ffadvert_latTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3String Te6edf3lon_arg Tff7b72= Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6ffadvert_lonTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3lat_argTb4b4b4.Te6edf3trimTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3lon_argTb4b4b4.Te6edf3trimTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3lat_argTb4b4b4.Te6edf3lengthTb4b4b4(Tb4b4b4) Tff7b72=Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{
Te6edf3boundary_stateTb4b4b4.Te6edf3advert_lat Tff7b72= T79c0ff0.0Tb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
Tffa657double Te6edf3lat_val Tff7b72= Te6edf3lat_argTb4b4b4.Te6edf3toDoubleTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3lat_val Tff7b72< T79c0ff-90.0 Tff7b72|Tff7b72| Te6edf3lat_val Tff7b72> T79c0ff90.0 Tff7b72|Tff7b72| Te6edf3isnanTb4b4b4(Te6edf3lat_valTb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3lat_val Tff7b72= T79c0ff0.0Tb4b4b4;
Tb4b4b4}
Te6edf3boundary_stateTb4b4b4.Te6edf3advert_lat Tff7b72= Te6edf3lat_valTb4b4b4;
Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3lon_argTb4b4b4.Te6edf3lengthTb4b4b4(Tb4b4b4) Tff7b72=Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{
Te6edf3boundary_stateTb4b4b4.Te6edf3advert_lon Tff7b72= T79c0ff0.0Tb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
Tffa657double Te6edf3lon_val Tff7b72= Te6edf3lon_argTb4b4b4.Te6edf3toDoubleTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3lon_val Tff7b72< T79c0ff-180.0 Tff7b72|Tff7b72| Te6edf3lon_val Tff7b72> T79c0ff180.0 Tff7b72|Tff7b72| Te6edf3isnanTb4b4b4(Te6edf3lon_valTb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3lon_val Tff7b72= T79c0ff0.0Tb4b4b4;
Tb4b4b4}
Te6edf3boundary_stateTb4b4b4.Te6edf3advert_lon Tff7b72= Te6edf3lon_valTb4b4b4;
Tb4b4b4}

Te6edf3boundary_stateTb4b4b4.Te6edf3advert_jitter Tff7b72= Tb4b4b4(Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6ffadvert_jitterTa5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4) Tff7b72=Tff7b72= T79c0ff1Tb4b4b4)Tb4b4b4;

T8b949e// ── Node name ──
Te6edf3String Te6edf3node_name_arg Tff7b72= Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6ffnode_nameTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3node_name_argTb4b4b4.Te6edf3trimTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3memsetTb4b4b4(Te6edf3boundary_stateTb4b4b4.Te6edf3node_nameTb4b4b4, T79c0ff0Tb4b4b4, Tff7b72sizeofTb4b4b4(Te6edf3boundary_stateTb4b4b4.Te6edf3node_nameTb4b4b4)Tb4b4b4)Tb4b4b4;
Te6edf3strncpyTb4b4b4(Te6edf3boundary_stateTb4b4b4.Te6edf3node_nameTb4b4b4, Te6edf3node_name_argTb4b4b4.Te6edf3c_strTb4b4b4(Tb4b4b4)Tb4b4b4, Tff7b72sizeofTb4b4b4(Te6edf3boundary_stateTb4b4b4.Te6edf3node_nameTb4b4b4) Tff7b72- T79c0ff1Tb4b4b4)Tb4b4b4;

T8b949e// Save boundary config to EEPROM
Te6edf3boundary_save_configTb4b4b4(Tb4b4b4)Tb4b4b4;

T8b949e// ── LoRa radio settings ──
Te6edf3String Te6edf3freq_str Tff7b72= Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6fffreqTa5d6ff"Tb4b4b4)Tb4b4b4;
Tffa657double Te6edf3freq_mhz Tff7b72= Te6edf3freq_strTb4b4b4.Te6edf3toDoubleTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3freq_mhz Tff7b72> T79c0ff0Tb4b4b4) Tb4b4b4{
Te6edf3lora_freq Tff7b72= Tb4b4b4(Tffa657uint32_tTb4b4b4)Tb4b4b4(Te6edf3freq_mhz Tff7b72* T79c0ff1000000.0Tb4b4b4)Tb4b4b4;
Tb4b4b4}

Te6edf3String Te6edf3bw_str Tff7b72= Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6ffbwTa5d6ff"Tb4b4b4)Tb4b4b4;
Tffa657uint32_t Te6edf3bw_val Tff7b72= Tb4b4b4(Tffa657uint32_tTb4b4b4)Te6edf3bw_strTb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3bw_val Tff7b72> T79c0ff0Tb4b4b4) Te6edf3lora_bw Tff7b72= Te6edf3bw_valTb4b4b4;

Tffa657int Te6edf3sf_val Tff7b72= Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6ffsfTa5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3sf_val Tff7b72>Tff7b72= T79c0ff5 Tff7b72&Tff7b72& Te6edf3sf_val Tff7b72<Tff7b72= T79c0ff12Tb4b4b4) Te6edf3lora_sf Tff7b72= Te6edf3sf_valTb4b4b4;

Tffa657int Te6edf3cr_val Tff7b72= Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6ffcrTa5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3cr_val Tff7b72>Tff7b72= T79c0ff5 Tff7b72&Tff7b72& Te6edf3cr_val Tff7b72<Tff7b72= T79c0ff8Tb4b4b4) Te6edf3lora_cr Tff7b72= Te6edf3cr_valTb4b4b4;

Tffa657int Te6edf3txp_val Tff7b72= Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6fftxpTa5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3txp_val Tff7b72>Tff7b72= T79c0ff2 Tff7b72&Tff7b72& Te6edf3txp_val Tff7b72<Tff7b72= T79c0ff30Tb4b4b4) Te6edf3lora_txp Tff7b72= Te6edf3txp_valTb4b4b4;

T8b949e// Save LoRa config to EEPROM (reuse existing eeprom_conf functions)
T8b949e// Write directly since hw_ready may not be set yet
Te6edf3eeprom_updateTb4b4b4(Te6edf3eeprom_addrTb4b4b4(Te6edf3ADDR_CONF_SFTb4b4b4)Tb4b4b4, Te6edf3lora_sfTb4b4b4)Tb4b4b4;
Te6edf3eeprom_updateTb4b4b4(Te6edf3eeprom_addrTb4b4b4(Te6edf3ADDR_CONF_CRTb4b4b4)Tb4b4b4, Te6edf3lora_crTb4b4b4)Tb4b4b4;
Te6edf3eeprom_updateTb4b4b4(Te6edf3eeprom_addrTb4b4b4(Te6edf3ADDR_CONF_TXPTb4b4b4)Tb4b4b4, Te6edf3lora_txpTb4b4b4)Tb4b4b4;
Te6edf3eeprom_updateTb4b4b4(Te6edf3eeprom_addrTb4b4b4(Te6edf3ADDR_CONF_BWTb4b4b4) Tff7b72+ T79c0ff0Tb4b4b4, Te6edf3lora_bw Tff7b72>Tff7b72> T79c0ff24Tb4b4b4)Tb4b4b4;
Te6edf3eeprom_updateTb4b4b4(Te6edf3eeprom_addrTb4b4b4(Te6edf3ADDR_CONF_BWTb4b4b4) Tff7b72+ T79c0ff1Tb4b4b4, Te6edf3lora_bw Tff7b72>Tff7b72> T79c0ff16Tb4b4b4)Tb4b4b4;
Te6edf3eeprom_updateTb4b4b4(Te6edf3eeprom_addrTb4b4b4(Te6edf3ADDR_CONF_BWTb4b4b4) Tff7b72+ T79c0ff2Tb4b4b4, Te6edf3lora_bw Tff7b72>Tff7b72> T79c0ff8Tb4b4b4)Tb4b4b4;
Te6edf3eeprom_updateTb4b4b4(Te6edf3eeprom_addrTb4b4b4(Te6edf3ADDR_CONF_BWTb4b4b4) Tff7b72+ T79c0ff3Tb4b4b4, Te6edf3lora_bwTb4b4b4)Tb4b4b4;
Te6edf3eeprom_updateTb4b4b4(Te6edf3eeprom_addrTb4b4b4(Te6edf3ADDR_CONF_FREQTb4b4b4) Tff7b72+ T79c0ff0Tb4b4b4, Te6edf3lora_freq Tff7b72>Tff7b72> T79c0ff24Tb4b4b4)Tb4b4b4;
Te6edf3eeprom_updateTb4b4b4(Te6edf3eeprom_addrTb4b4b4(Te6edf3ADDR_CONF_FREQTb4b4b4) Tff7b72+ T79c0ff1Tb4b4b4, Te6edf3lora_freq Tff7b72>Tff7b72> T79c0ff16Tb4b4b4)Tb4b4b4;
Te6edf3eeprom_updateTb4b4b4(Te6edf3eeprom_addrTb4b4b4(Te6edf3ADDR_CONF_FREQTb4b4b4) Tff7b72+ T79c0ff2Tb4b4b4, Te6edf3lora_freq Tff7b72>Tff7b72> T79c0ff8Tb4b4b4)Tb4b4b4;
Te6edf3eeprom_updateTb4b4b4(Te6edf3eeprom_addrTb4b4b4(Te6edf3ADDR_CONF_FREQTb4b4b4) Tff7b72+ T79c0ff3Tb4b4b4, Te6edf3lora_freqTb4b4b4)Tb4b4b4;
Te6edf3eeprom_updateTb4b4b4(Te6edf3eeprom_addrTb4b4b4(Te6edf3ADDR_CONF_OKTb4b4b4)Tb4b4b4, Te6edf3CONF_OK_BYTETb4b4b4)Tb4b4b4;

Te6edf3EEPROMTb4b4b4.Te6edf3commitTb4b4b4(Tb4b4b4)Tb4b4b4;

T8b949e// ── Send confirmation page ──
Te6edf3String Te6edf3ok Tff7b72= Te6edf3FTb4b4b4(
Ta5d6ff"Ta5d6ff<!DOCTYPE html><html><head>Ta5d6ff"
Ta5d6ff"Ta5d6ff<meta name='viewport' content='width=device-width,initial-scale=1'>Ta5d6ff"
Ta5d6ff"Ta5d6ff<title>Saved</title>Ta5d6ff"
Ta5d6ff"Ta5d6ff<style>Ta5d6ff"
Ta5d6ff"Ta5d6ffbody{font-family:sans-serif;background:#1a1a2e;color:#e0e0e0;padding:40px;Ta5d6ff"
Ta5d6ff"Ta5d6ffdisplay:flex;align-items:center;justify-content:center;min-height:80vh;}Ta5d6ff"
Ta5d6ff"Ta5d6ff.ok{background:#16213e;padding:30px;border-radius:12px;text-align:center;max-width:400px;}Ta5d6ff"
Ta5d6ff"Ta5d6ffh1{color:#4caf50;margin-bottom:16px;}Ta5d6ff"
Ta5d6ff"Ta5d6ffp{color:#aaa;}Ta5d6ff"
Ta5d6ff"Ta5d6ff</style></head><body>Ta5d6ff"
Ta5d6ff"Ta5d6ff<div class='ok'>Ta5d6ff"
Ta5d6ff"Ta5d6ff<h1>&#x2705; Configuration Saved</h1>Ta5d6ff"
Ta5d6ff"Ta5d6ff<p>Device will reboot in 3 seconds and connect to your WiFi network.</p>Ta5d6ff"
Ta5d6ff"Ta5d6ff<p style='color:#666;font-size:0.85em;'>If the device cannot connect, hold the button for 5+ seconds to re-enter setup.</p>Ta5d6ff"
Ta5d6ff"Ta5d6ff</div></body></html>Ta5d6ff"
Tb4b4b4)Tb4b4b4;
Te6edf3config_serverTff7b72-Tff7b72>Te6edf3sendTb4b4b4(T79c0ff200Tb4b4b4, Ta5d6ff"Ta5d6fftext/htmlTa5d6ff"Tb4b4b4, Te6edf3okTb4b4b4)Tb4b4b4;

T8b949e// Give the response time to send
Te6edf3delayTb4b4b4(T79c0ff3000Tb4b4b4)Tb4b4b4;

T8b949e// Reboot
Te6edf3ESPTb4b4b4.Te6edf3restartTb4b4b4(Tb4b4b4)Tb4b4b4;
Tb4b4b4}

T8b949e// ─── Captive Portal redirect ─────────────────────────────────────────────────
Tff7b72static Tffa657void Td2a8ffconfig_handle_redirectTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3config_serverTff7b72-Tff7b72>Te6edf3sendHeaderTb4b4b4(Ta5d6ff"Ta5d6ffLocationTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffhttp://10.0.0.1/Ta5d6ff"Tb4b4b4, Tffa657trueTb4b4b4)Tb4b4b4;
Te6edf3config_serverTff7b72-Tff7b72>Te6edf3sendTb4b4b4(T79c0ff302Tb4b4b4, Ta5d6ff"Ta5d6fftext/plainTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffRedirecting to setup...Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tb4b4b4}

T8b949e// ─── Check if config is needed ───────────────────────────────────────────────
Tffa657bool Td2a8ffboundary_needs_configTb4b4b4(Tb4b4b4) Tb4b4b4{
T8b949e// If the RTNode app marker is missing, this node was either never
T8b949e// configured by RTNode or was flashed from a different firmware family
T8b949e// such as stock RNode. Force the portal so RTNode can claim and rewrite
T8b949e// its persisted settings explicitly.
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3boundary_app_marker_validTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Tff7b72return Tffa657trueTb4b4b4;
Tb4b4b4}

T8b949e// Check if WiFi SSID is configured
Tffa657char Te6edf3ssidTb4b4b4[T79c0ff33Tb4b4b4]Tb4b4b4;
Tff7b72for Tb4b4b4(Tffa657int Te6edf3i Tff7b72= T79c0ff0Tb4b4b4; Te6edf3i Tff7b72< T79c0ff32Tb4b4b4; Te6edf3iTff7b72+Tff7b72+Tb4b4b4) Tb4b4b4{
Te6edf3ssidTb4b4b4[Te6edf3iTb4b4b4] Tff7b72= Te6edf3EEPROMTb4b4b4.Te6edf3readTb4b4b4(Te6edf3config_addrTb4b4b4(Te6edf3ADDR_CONF_SSID Tff7b72+ Te6edf3iTb4b4b4)Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3ssidTb4b4b4[Te6edf3iTb4b4b4] Tff7b72=Tff7b72= Tb4b4b4(Tffa657charTb4b4b4)T79c0ff0xFFTb4b4b4) Te6edf3ssidTb4b4b4[Te6edf3iTb4b4b4] Tff7b72= Ta5d6ff'Ta5d6ff\0Ta5d6ff'Tb4b4b4;
Tb4b4b4}
Te6edf3ssidTb4b4b4[T79c0ff32Tb4b4b4] Tff7b72= Ta5d6ff'Ta5d6ff\0Ta5d6ff'Tb4b4b4;

T8b949e// Also check boundary mode enable flag
Tffa657uint8_t Te6edf3bmode Tff7b72= Te6edf3EEPROMTb4b4b4.Te6edf3readTb4b4b4(Te6edf3config_addrTb4b4b4(Te6edf3ADDR_CONF_BMODETb4b4b4)Tb4b4b4)Tb4b4b4;

T8b949e// Need config if no SSID set and boundary not yet configured
Tff7b72if Tb4b4b4(Te6edf3ssidTb4b4b4[T79c0ff0Tb4b4b4] Tff7b72=Tff7b72= Ta5d6ff'Ta5d6ff\0Ta5d6ff' Tff7b72&Tff7b72& Te6edf3bmode Tff7b72!Tff7b72= Te6edf3BOUNDARY_ENABLE_BYTETb4b4b4) Tb4b4b4{
Tff7b72return Tffa657trueTb4b4b4;
Tb4b4b4}
Tff7b72return Tffa657falseTb4b4b4;
Tb4b4b4}

T8b949e// ─── Start Config Portal ─────────────────────────────────────────────────────
Tffa657void Td2a8ffconfig_portal_startTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3config_portal_activeTb4b4b4) Tff7b72returnTb4b4b4;

Te6edf3SerialTb4b4b4.Te6edf3printlnTb4b4b4(Ta5d6ff"Ta5d6ff[Config] Starting configuration portal...Ta5d6ff"Tb4b4b4)Tb4b4b4;

T8b949e// Stop any existing WiFi
Te6edf3WiFiTb4b4b4.Te6edf3softAPdisconnectTb4b4b4(Tffa657trueTb4b4b4)Tb4b4b4;
Te6edf3WiFiTb4b4b4.Te6edf3disconnectTb4b4b4(Tffa657trueTb4b4b4, Tffa657trueTb4b4b4)Tb4b4b4;
Te6edf3WiFiTb4b4b4.Te6edf3modeTb4b4b4(Te6edf3WIFI_MODE_NULLTb4b4b4)Tb4b4b4;
Te6edf3delayTb4b4b4(T79c0ff100Tb4b4b4)Tb4b4b4;

T8b949e// Start AP
Te6edf3WiFiTb4b4b4.Te6edf3modeTb4b4b4(Te6edf3WIFI_APTb4b4b4)Tb4b4b4;
Te6edf3WiFiTb4b4b4.Te6edf3softAPTb4b4b4(Te6edf3CONFIG_AP_SSIDTb4b4b4, Tffa657NULLTb4b4b4)Tb4b4b4; T8b949e// Open AP for easy setup
Te6edf3delayTb4b4b4(T79c0ff150Tb4b4b4)Tb4b4b4;

Te6edf3IPAddress Te6edf3ap_addrTb4b4b4(T79c0ff10Tb4b4b4, T79c0ff0Tb4b4b4, T79c0ff0Tb4b4b4, T79c0ff1Tb4b4b4)Tb4b4b4;
Te6edf3IPAddress Te6edf3ap_maskTb4b4b4(T79c0ff255Tb4b4b4, T79c0ff255Tb4b4b4, T79c0ff255Tb4b4b4, T79c0ff0Tb4b4b4)Tb4b4b4;
Te6edf3WiFiTb4b4b4.Te6edf3softAPConfigTb4b4b4(Te6edf3ap_addrTb4b4b4, Te6edf3ap_addrTb4b4b4, Te6edf3ap_maskTb4b4b4)Tb4b4b4;

Te6edf3SerialTb4b4b4.Te6edf3printTb4b4b4(Ta5d6ff"Ta5d6ff[Config] AP started: Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3SerialTb4b4b4.Te6edf3printlnTb4b4b4(Te6edf3CONFIG_AP_SSIDTb4b4b4)Tb4b4b4;
Te6edf3SerialTb4b4b4.Te6edf3printTb4b4b4(Ta5d6ff"Ta5d6ff[Config] IP: Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3SerialTb4b4b4.Te6edf3printlnTb4b4b4(Te6edf3WiFiTb4b4b4.Te6edf3softAPIPTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4;

T8b949e// Start DNS server for captive portal (redirect all domains to us)
Te6edf3config_dns Tff7b72= Te6edf3new Te6edf3DNSServerTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3config_dnsTff7b72-Tff7b72>Te6edf3startTb4b4b4(Te6edf3DNS_PORTTb4b4b4, Ta5d6ff"Ta5d6ff*Ta5d6ff"Tb4b4b4, Te6edf3ap_addrTb4b4b4)Tb4b4b4;

T8b949e// Start web server
Te6edf3config_server Tff7b72= Te6edf3new Te6edf3WebServerTb4b4b4(Te6edf3HTTP_PORTTb4b4b4)Tb4b4b4;
Te6edf3config_serverTff7b72-Tff7b72>Te6edf3onTb4b4b4(Ta5d6ff"Ta5d6ff/Ta5d6ff"Tb4b4b4, Te6edf3HTTP_GETTb4b4b4, Te6edf3config_send_htmlTb4b4b4)Tb4b4b4;
Te6edf3config_serverTff7b72-Tff7b72>Te6edf3onTb4b4b4(Ta5d6ff"Ta5d6ff/saveTa5d6ff"Tb4b4b4, Te6edf3HTTP_POSTTb4b4b4, Te6edf3config_handle_saveTb4b4b4)Tb4b4b4;
Te6edf3config_serverTff7b72-Tff7b72>Te6edf3onNotFoundTb4b4b4(Te6edf3config_handle_redirectTb4b4b4)Tb4b4b4; T8b949e// Captive portal catch-all
Te6edf3config_serverTff7b72-Tff7b72>Te6edf3beginTb4b4b4(Tb4b4b4)Tb4b4b4;

Te6edf3config_portal_active Tff7b72= Tffa657trueTb4b4b4;

Te6edf3SerialTb4b4b4.Te6edf3printlnTb4b4b4(Ta5d6ff"Ta5d6ff[Config] Portal ready — connect to WiFi: Ta5d6ff" Tff7b72+ Te6edf3StringTb4b4b4(Te6edf3CONFIG_AP_SSIDTb4b4b4)Tb4b4b4)Tb4b4b4;

Tff7b72#Tff7b72if HAS_DISPLAY
Tff7b72if Tb4b4b4(Te6edf3disp_readyTb4b4b4) Tb4b4b4{
T8b949e// Show config mode on display
Te6edf3stat_areaTb4b4b4.Te6edf3fillScreenTb4b4b4(Te6edf3SSD1306_BLACKTb4b4b4)Tb4b4b4;
Te6edf3stat_areaTb4b4b4.Te6edf3setCursorTb4b4b4(T79c0ff0Tb4b4b4, T79c0ff0Tb4b4b4)Tb4b4b4;
Te6edf3stat_areaTb4b4b4.Te6edf3printlnTb4b4b4(Ta5d6ff"Ta5d6ffCONFIG MODETa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3stat_areaTb4b4b4.Te6edf3printlnTb4b4b4(Ta5d6ff"Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3stat_areaTb4b4b4.Te6edf3printlnTb4b4b4(Ta5d6ff"Ta5d6ffConnect to:Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3stat_areaTb4b4b4.Te6edf3printlnTb4b4b4(Te6edf3CONFIG_AP_SSIDTb4b4b4)Tb4b4b4;
Te6edf3stat_areaTb4b4b4.Te6edf3printlnTb4b4b4(Ta5d6ff"Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3stat_areaTb4b4b4.Te6edf3printlnTb4b4b4(Ta5d6ff"Ta5d6ffOpen browserTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3stat_areaTb4b4b4.Te6edf3printlnTb4b4b4(Ta5d6ff"Ta5d6ffhttp://10.0.0.1Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3displayTb4b4b4.Te6edf3clearDisplayTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3displayTb4b4b4.Te6edf3drawBitmapTb4b4b4(T79c0ff0Tb4b4b4, T79c0ff0Tb4b4b4, Te6edf3stat_areaTb4b4b4.Te6edf3getBufferTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3stat_areaTb4b4b4.Te6edf3widthTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3stat_areaTb4b4b4.Te6edf3heightTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3SSD1306_WHITETb4b4b4, Te6edf3SSD1306_BLACKTb4b4b4)Tb4b4b4;
Te6edf3displayTb4b4b4.Te6edf3displayTb4b4b4(Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Tff7b72#Tff7b72endif
T8b949e// Headless: LED ramp will be driven from the WCC portal loop
Tff7b72if Tb4b4b4(Te6edf3headless_modeTb4b4b4) Tb4b4b4{
Te6edf3SerialTb4b4b4.Te6edf3printlnTb4b4b4(Ta5d6ff"Ta5d6ff[Config] Headless mode — LED will breathe during config portalTa5d6ff"Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Tb4b4b4}

T8b949e// ─── Stop Config Portal ──────────────────────────────────────────────────────
Tffa657void Td2a8ffconfig_portal_stopTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3config_portal_activeTb4b4b4) Tff7b72returnTb4b4b4;

Te6edf3SerialTb4b4b4.Te6edf3printlnTb4b4b4(Ta5d6ff"Ta5d6ff[Config] Stopping configuration portalTa5d6ff"Tb4b4b4)Tb4b4b4;

Tff7b72if Tb4b4b4(Te6edf3config_serverTb4b4b4) Tb4b4b4{
Te6edf3config_serverTff7b72-Tff7b72>Te6edf3stopTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3delete Te6edf3config_serverTb4b4b4;
Te6edf3config_server Tff7b72= Tffa657nullptrTb4b4b4;
Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3config_dnsTb4b4b4) Tb4b4b4{
Te6edf3config_dnsTff7b72-Tff7b72>Te6edf3stopTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3delete Te6edf3config_dnsTb4b4b4;
Te6edf3config_dns Tff7b72= Tffa657nullptrTb4b4b4;
Tb4b4b4}

Te6edf3WiFiTb4b4b4.Te6edf3softAPdisconnectTb4b4b4(Tffa657trueTb4b4b4)Tb4b4b4;
Te6edf3WiFiTb4b4b4.Te6edf3modeTb4b4b4(Te6edf3WIFI_MODE_NULLTb4b4b4)Tb4b4b4;
Te6edf3config_portal_active Tff7b72= Tffa657falseTb4b4b4;
Tb4b4b4}

T8b949e// ─── Portal Loop — call from main loop() ─────────────────────────────────────
Tffa657void Td2a8ffconfig_portal_loopTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3config_portal_activeTb4b4b4) Tff7b72returnTb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3config_dnsTb4b4b4) Te6edf3config_dnsTff7b72-Tff7b72>Te6edf3processNextRequestTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3config_serverTb4b4b4) Te6edf3config_serverTff7b72-Tff7b72>Te6edf3handleClientTb4b4b4(Tb4b4b4)Tb4b4b4;
Tb4b4b4}

T8b949e// ─── Is portal active? ──────────────────────────────────────────────────────
Tffa657bool Td2a8ffconfig_portal_is_activeTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72return Te6edf3config_portal_activeTb4b4b4;
Tb4b4b4}

Tff7b72#Tff7b72endif T8b949e// BOUNDARY_MODE
Tff7b72#Tff7b72endif T8b949e// BOUNDARY_CONFIG_H

Served by rngit 1.4.2 - Generated in 0.08s